home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-07-16 | 20.3 KB | 754 lines | [TEXT/CWIE] |
- // ===========================================================================
- // CPopulation.cp ©1995-97 Timo Eloranta All rights reserved.
- // ===========================================================================
- // CPopulation.h (press Command-Tab to open the associated header)
-
- #include <UDrawingState.h>
- #include <UDesktop.h>
-
- #ifndef __TOOLUTILS__
- #include <ToolUtils.h>
- #endif
-
- #include <profiler.h>
- #include <algorithm.h> // MSL - sort
-
- #include "CPopulation.h"
- #include "CGraphGAApp.h"
- #include "MyUtils.h"
- #include "GraphGA_PaneIDs.h"
- #include "LAnimateCursor.h"
-
- // ---------------------------------------------------------------------------
- // • ~CPopulation
- // ---------------------------------------------------------------------------
- // Destructor.
-
- CPopulation::~CPopulation()
- {
- JunkGraphs();
- }
-
- // ---------------------------------------------------------------------------
- // • JunkGraphs
- //
- // Called by: CPopulation::~CPopulation
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::JunkGraphs()
- {
- // Get rid of the graphs (chromosomes)
-
- GraphPtrVector::iterator theIter = mDrawings.begin();
-
- while ( theIter != mDrawings.end() ) {
- delete *theIter;
- ++theIter;
- }
-
- mDrawings.erase( mDrawings.begin(), mDrawings.end() );
- }
-
- // ---------------------------------------------------------------------------
- // • Initialize
- //
- // Called by: CGraphGADoc::CGraphGADoc
- // CGraphGADoc::ObeyCommand
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::Initialize(
- Boolean inFirstTime,
- SGraphSize *inGraphSize,
- SNodePairVector *inEdgeArray )
- {
- Int16 theIndex;
- CGraphPtr theGraph;
-
- mCurGeneration = 0L;
- mRunningTime = 0;
-
- // Use "spin cursor" since this can take a while...
- LAnimateCursor theSpinCursor( curs_First, curs_Qty );
-
- if ( inFirstTime ) {
-
- mNodeCount = inGraphSize -> nodes;
- mEdgeCount = inGraphSize -> edges;
-
- mSelectionArray.reserve( mPopSize + 1 );
-
- // Create the list of graph drawings with randomly placed nodes
- for ( theIndex = 1; theIndex <= mPopSize; ++theIndex ) {
-
- theGraph = new CGraphDrawing;
- if ( theGraph ) {
- theSpinCursor.Set();
- theGraph -> Initialize( mNodeCount, mEdgeCount,
- mGridSize, true );
-
- mDrawings.push_back( theGraph );
- }
- }
-
- // Initialize the two extra graphs which are used in crossover
- // They don't actually need the randomly placed nodes, but
- // we'll throw them away later (in CGraphNodes::Crossover) ...
- mCrossTemp1.Initialize( mNodeCount, mEdgeCount, mGridSize, true );
- mCrossTemp2.Initialize( mNodeCount, mEdgeCount, mGridSize, true );
-
- if ( mEdgeCount > 36 )
- AddCrossMemories();
-
- SetUpEdges( inEdgeArray );
-
- } else { // We already have a list of drawings.
- // We just want to reshuffle the positions of the nodes.
-
- GraphPtrVector::iterator theIter = mDrawings.begin();
-
- while ( theIter != mDrawings.end() ) {
- theSpinCursor.Set();
- (**theIter).Initialize( mNodeCount, mEdgeCount, mGridSize, false );
- ++theIter;
- }
- }
-
- InitBestEver();
-
- Evaluate( &theSpinCursor ); // Evaluation of the initial population
- }
-
- // ---------------------------------------------------------------------------
- // • AddCrossMemories (PRIVATE)
- //
- // Called by: CPopulation::Initialize
- // ---------------------------------------------------------------------------
- // Try adding a "cross memory" to every drawing. If any of the drawings fail
- // to get a memory (if there isn't enough memory...), remove memories from
- // all the drawings!
- //
- // Note: mBestEver, mCrossTemp1 & mCrossTemp2 don't need cross memories
-
- void
- CPopulation::AddCrossMemories()
- {
- GraphPtrVector::iterator theIter = mDrawings.begin();
-
- Try_ {
- while ( theIter != mDrawings.end() ) {
- (*theIter) -> AddCrossMemory();
- ++theIter;
- }
- }
-
- Catch_( inErr ) {
- theIter = mDrawings.begin();
-
- while ( theIter != mDrawings.end() ) {
- (*theIter) -> JunkCrossMemory();
- ++theIter;
- }
-
- if ( mEdgeCount <= 256 ) {
- UDesktop::Deactivate();
- ::NoteAlert( ALRT_Memory, nil );
- UDesktop::Activate();
- }
-
- } EndCatch_
- }
-
- // ---------------------------------------------------------------------------
- // • SetUpEdges (PRIVATE)
- //
- // Called by: CPopulation::Initialize
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::SetUpEdges( SNodePairVector *inEdgeArray )
- {
- Int16 theIndex,
- theNodeNbr1, theNodeNbr2;
- CGraphPtr theFirstGraph;
- Boolean theRandomEdges = (! inEdgeArray );
-
- theFirstGraph = mDrawings.front();
-
- theIndex = 1;
- while (theIndex <= mEdgeCount) { // Add edges to graph drawings
-
- if ( theRandomEdges ) { // Are the nodes picked randomly?
- theNodeNbr1 = RangedRdm( 1, mNodeCount );
- theNodeNbr2 = RangedRdm( 1, mNodeCount );
- } else {
- theNodeNbr1 = (*inEdgeArray)[ theIndex ].nodeNbr1;
- theNodeNbr2 = (*inEdgeArray)[ theIndex ].nodeNbr2;
- }
-
- if ( theNodeNbr1 != theNodeNbr2 ) {
-
- // If necessary, test with the 1st graph
- // whether this edge will do...
- if ( (! theRandomEdges ) || (theFirstGraph ->
- ValidNewEdge( theNodeNbr1, theNodeNbr2 )) ) {
-
- // Iterate through all the graphs and insert
- // the new *same* edge to everyone of them...
- // Note: mCrossTemp1 and mCrossTemp2 need no edges!
-
- GraphPtrVector::iterator theIter = mDrawings.begin();
-
- while ( theIter != mDrawings.end() ) {
- (*theIter) -> SetNewEdge( theIndex,
- theNodeNbr1, theNodeNbr2);
- ++theIter;
- }
-
- ++theIndex; // One edge done, many to go...
- }
- }
- }
- }
-
- // ---------------------------------------------------------------------------
- // • InitSelectionArray (PRIVATE)
- //
- // Called by: CPopulation::SetSelection
- // ---------------------------------------------------------------------------
- // The last value (mSelectionArray[ mPopSize ]) is always 100.
- // The Nth value = the (N+1)th value +
- // the max( 100 - (mPopSize - N) * mSelection.step,
- // mSelection.min )
- //
- // Example. mPopSize: 8, mSelection.step: 15, mSelection.min: 20
- // (the array is filled starting from the end!)
- //
- // ---> increase: { -, 20, 20, 25, 40, 55, 70, 85, 100 }
- // ---> mSelectionArray: { 0, 415, 395, 375, 350, 310, 255, 185, 100 }
-
- void
- CPopulation::InitSelectionArray()
- {
- Int16 theDex, theElevator = 100;
-
- mSelectionArray[ 0 ] = 0;
-
- // If the user hasn't set a step then we'll count one
-
- if ( mSelection.autom )
- mSelection.step = AutomSelectionStep( mSelection.min, mPopSize );
-
- for ( theDex = mPopSize; theDex >= 1; theDex-- ) {
- if ( theDex == mPopSize )
- mSelectionArray[theDex] = 100;
- else
- mSelectionArray[theDex] = mSelectionArray[ theDex + 1 ] +
- max( theElevator, mSelection.min );
-
- theElevator -= mSelection.step;
- }
- }
-
- // ---------------------------------------------------------------------------
- // • AutomSelectionStep
- //
- // Called by: CPopulation::InitSelectionArray
- // CSelectionDialog::SetValues
- // CSelectionDialog::ListenToMessage
- // ---------------------------------------------------------------------------
-
- Int16
- AutomSelectionStep(
- Int16 inSelectionMinimum,
- Int16 inPopSize )
- {
- return (2 * (100 - inSelectionMinimum) ) / inPopSize;
- }
-
- // ---------------------------------------------------------------------------
- // • InitBestEver (PRIVATE)
- //
- // Called by: CPopulation::Initialize
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::InitBestEver()
- {
- CGraphPtr theFirstGraph;
- CFitness theDummyFitness;
-
- // Copy one (= 1st) of the graphs to be the initial best ever,
- // but before that evaluate the graph so that the best ever
- // doesn't need to be evaluated...
-
- theFirstGraph = mDrawings.front();
- theFirstGraph -> GetFitness( theDummyFitness, max_Int32 );
-
- mBestEver.BestCopy( *theFirstGraph, true );
-
- mBestGeneration = 0L;
- mBestTime = 0L;
- }
-
- // ---------------------------------------------------------------------------
- // • Evaluate
- //
- // Called by: CPopulation::Initialize
- // CPopulation::DoOneIteration
- // ---------------------------------------------------------------------------
-
- Boolean
- CPopulation::Evaluate(
- LAnimateCursor *inSpinCursor )
- {
- CGraphPtr theNewChamp = nil;
- CFitness theFitness, theCurHScore;
- Int32 theCrossings, theCrossingsSum = 0;
-
- GraphPtrVector::iterator theIter = mDrawings.begin();
-
- mLeastCrossings = max_Int32;
-
- mBestEver.GetFitness( theCurHScore );
-
- while ( theIter != mDrawings.end() ) {
-
- if ( inSpinCursor )
- inSpinCursor -> Set();
-
- (*theIter) -> GetFitness( theFitness, mLeastCrossings );
-
- theCrossings = theFitness.GetCrossings();
-
- if ( theCrossings < mLeastCrossings )
- mLeastCrossings = theCrossings;
-
- theCrossingsSum += theCrossings;
-
- if ( theCurHScore < theFitness ) {
- theCurHScore = theFitness;
- theNewChamp = *theIter;
- }
- ++theIter;
- }
-
- mAvgCrossings = (mPopSize > 0) ? (theCrossingsSum / mPopSize) : -1;
-
- if ( theNewChamp ) {
- mBestEver.BestCopy( *theNewChamp, false );
-
- return true; // The best ever changed !!
- }
- return false; // The best ever didn't change...
- }
-
- // ---------------------------------------------------------------------------
- // • DoIterate
- //
- // Called by: CGraphGADoc::SpendTime
- // ---------------------------------------------------------------------------
- // Spend time iterating. Exit if
- // A) the time specified in inMaxSpendTimeOnce is reached
- // or B) our termination condition for iteration is filled.
- //
- // Pass back whether we stopped because the termination condition was
- // reached (outGameIsOver) and also if we found a new best solution
- // (outThereIsANewKing).
-
- void
- CPopulation::DoIterate(
- Boolean &outGameIsOver,
- Boolean &outThereIsANewKing,
- Int16 inMaxSpendTimeOnce )
- {
- Int32 theSessionStartingTicks, theIterStartingTicks, theTimeNow;
- Boolean theTimeIsOut = false;
-
- theSessionStartingTicks = ::TickCount();
-
- // We go for another iteration if the termination condition is not
- // filled and we haven't ran out of time...
-
- while ( (! (outGameIsOver = ( mTermCondExists &&
- TerminationCondition() )))
- &&
- (! theTimeIsOut)
- ) {
-
- // Another iteration is wanted. So, let's do one!
- // Before we go, we'll start the clock...
-
- theIterStartingTicks = ::TickCount();
-
- #if __profile__
- ProfilerSetStatus( true ); // ••• Start profiling •••
- #endif
-
- if ( DoOneIteration() ) {
- outThereIsANewKing = true; // A new "best ever" was found
- mBestGeneration = mCurGeneration;
- }
-
- #if __profile__
- ProfilerSetStatus( false ); // ••• Stop profiling •••
- #endif
-
- theTimeNow = ::TickCount();
-
- // Add the time of last iteration to the total
- mRunningTime += theTimeNow - theIterStartingTicks;
-
- if ( mBestGeneration == mCurGeneration )
- mBestTime = mRunningTime;
-
- // Check if we've already spent enough time for one "session"
- theTimeIsOut =
- ( ( theTimeNow - theSessionStartingTicks) > inMaxSpendTimeOnce );
- }
- }
-
- // ---------------------------------------------------------------------------
- // • DoOneIteration (PRIVATE)
- //
- // Called by: CPopulation::DoIterate
- // ---------------------------------------------------------------------------
-
- Boolean
- CPopulation::DoOneIteration( )
- {
- mCurGeneration++;
-
- DoSelection();
-
- DoCrossover();
- DoMutate();
-
- return Evaluate();
- }
-
- // ---------------------------------------------------------------------------
- // • DoSelection (PRIVATE)
- //
- // Called by: CPopulation::DoOneIteration
- // ---------------------------------------------------------------------------
- // Select surviving chromosomes using linear normalization (+ elitism)
-
- void
- CPopulation::DoSelection( )
- {
- UInt16Vector theDyingOnes;
- UInt16Vector theSelectionCounts( mPopSize + 1, 0 );
- Int16 theSelectCount = mPopSize,
- theCount, theIndex, theCloneIndex;
-
- CGraphPtr theGraph1, theGraph2;
-
- // We'll start by sorting the list of graph drawings
- sort(
- mDrawings.begin(),
- mDrawings.end(),
- mComparator
- );
-
- // If elitism is used, we'll make sure that the best won't get dropped!
- // The best is situated at the end of the sorted list...
- if ( mSelection.elitism ) {
- theSelectionCounts[ mPopSize ] = 1;
- theSelectCount--;
- }
-
- // Use random numbers to choose the new intermediate population.
- // Keep track of how many selections each chromosome gets.
- for ( theCount = 0; theCount < theSelectCount; ++theCount )
- ++theSelectionCounts[ SelectOne() ];
-
- // Collect the ones that got no selections to the "list of death"...
- for ( theIndex = 1; theIndex <= mPopSize; ++theIndex )
- if ( ! theSelectionCounts[ theIndex ] )
- theDyingOnes.push_back( theIndex );
-
- // Finally go through the selection array and copy ones
- // with >1 selections to the places of the ones with no future...
- for ( theIndex = 1; theIndex <= mPopSize; ++theIndex ) {
-
- if ( theSelectionCounts[ theIndex ] ) {
- theGraph1 = mDrawings[ theIndex - 1 ];
- theGraph1 -> SetSelectionOriginal( theIndex );
-
- while ( theSelectionCounts[ theIndex ] > 1 ) {
- theCloneIndex = theDyingOnes.back();
- theDyingOnes.pop_back();
- theGraph2 = mDrawings[ theCloneIndex - 1 ];
- theGraph2 -> RuntimeCopy( *theGraph1, true ); // Clone !
-
- theSelectionCounts[ theIndex ]--;
- }
- }
- }
- }
-
- // ---------------------------------------------------------------------------
- // • SelectOne (PRIVATE)
- //
- // Called by: CPopulation::DoSelection
- // ---------------------------------------------------------------------------
-
- Int16
- CPopulation::SelectOne( )
- {
- Int16 theRandomNbr,
- theGraphNbr = mPopSize;
-
- // mSelectionArray[1] holds the cumulative sum of all
- // the items in the selection array...
- theRandomNbr = RangedRdm( 1, mSelectionArray[ 1 ] );
-
- while ( mSelectionArray[ theGraphNbr ] < theRandomNbr )
- theGraphNbr--;
-
- return theGraphNbr;
- }
-
- // ---------------------------------------------------------------------------
- // • DoMutate (PRIVATE)
- //
- // Called by: CPopulation::DoOneIteration
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::DoMutate( )
- {
- GraphPtrVector::iterator theIter = mDrawings.begin();
- GraphPtrVector::iterator theEnd;
-
- // If elitism is used then we protect the best chromosome
-
- if ( mSelection.elitism )
- theEnd = &mDrawings.back();
- else
- theEnd = mDrawings.end();
-
- while ( theIter != theEnd ) {
- if ( mMutationProb > FastRandom(100) )
- (*theIter) -> Mutate();
- ++theIter;
- }
- }
-
- // ---------------------------------------------------------------------------
- // • DoCrossover (PRIVATE)
- //
- // Called by: CPopulation::DoOneIteration
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::DoCrossover( )
- {
- CGraphPtr theCrosser1, theCrosser2;
- GraphPtrVector theCrossers;
- Int32 theCrosserCount = 0, theIndex;
-
- GraphPtrVector::iterator theIter = mDrawings.begin();
- GraphPtrVector::iterator theEnd;
-
- // We start by selecting the graph drawings which
- // will take part in this phase.
-
- // If elitism is used then we protect the best chromosome
-
- if ( mSelection.elitism )
- theEnd = &mDrawings.back();
- else
- theEnd = mDrawings.end();
-
- while ( theIter != theEnd ) {
- if ( mCrossoverProb > FastRandom(100) )
- theCrossers.push_back( *theIter );
- ++theIter;
- }
-
- // We need an even number of graphs! If we have an odd number,
- // then just throw one away by random...
-
- theCrosserCount = theCrossers.size();
- if ( theCrosserCount % 2 ) {
- theCrossers.erase( theCrossers.begin() + FastRandom( theCrosserCount ));
- theCrosserCount--;
- }
-
- // Now we start pairing the chromosomes (=graphs) and crossing
- // them over. Every graph takes part in max one crossover...
-
- while ( theCrosserCount > 1 ) {
-
- theCrosser1 = theCrossers[ theIndex = FastRandom( theCrosserCount ) ];
- theCrossers.erase( theCrossers.begin() + theIndex );
- theCrosserCount--;
- theCrosser2 = theCrossers[ theIndex = FastRandom( theCrosserCount ) ];
- theCrossers.erase( theCrossers.begin() + theIndex );
- theCrosserCount--;
-
- // Now we have two graphs in theCrosser1 and theCrosser2.
- // We cross them only if they are not the one and same
- // (this is possible if they are duplicates from the selection...).
-
- if ( (theCrosser1 -> GetSelectionOriginal()) !=
- (theCrosser2 -> GetSelectionOriginal()) ) {
-
- if ( RandomBool() )
- theCrosser1 -> ThreeNodeCrossover( theCrosser2 ); // 50 %
- else
- DoRectCrossover( theCrosser1, theCrosser2 ); // 50 %
- }
- }
- }
-
- // ---------------------------------------------------------------------------
- // • DoRectCrossover (PRIVATE)
- //
- // Called by: CPopulation::DoCrossover
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::DoRectCrossover(
- CGraphPtr inCrosser1,
- CGraphPtr inCrosser2 )
- {
- Int16 theW, theH; // = width and height of rect
- Rect theSourceRect, theDestRect;
-
- // Next we'll choose the "crossover rects". We'll restrict
- // both width and height of the rect to one-third-of-a-grid.
-
- if ( mGridSize < 6 )
- theW = theH = 1;
- else {
- theW = RangedRdm( 1, mGridSize / 3 );
- theH = RangedRdm( 1, mGridSize / 3 );
- }
-
- theSourceRect.left = RangedRdm( 1, mGridSize - theW + 1 );
- theSourceRect.right = theSourceRect.left + theW - 1;
- theSourceRect.top = RangedRdm( 1, mGridSize - theH + 1 );
- theSourceRect.bottom = theSourceRect.top + theH - 1;
-
- // We give a 50/50 chance for the destination rectangle
- // to be totally random or the same as the source rect...
-
- if ( RandomBool() ) {
-
- theDestRect.left = RangedRdm( 1, mGridSize - theW + 1 );
- theDestRect.right = theDestRect.left + theW - 1;
- theDestRect.top = RangedRdm( 1, mGridSize - theH + 1 );
- theDestRect.bottom = theDestRect.top + theH - 1;
-
- } else
- theDestRect = theSourceRect;
-
- // Cross em'!
- mCrossTemp1.RectCrossover( theSourceRect, theDestRect,
- *inCrosser1, *inCrosser2 );
- mCrossTemp2.RectCrossover( theSourceRect, theDestRect,
- *inCrosser2, *inCrosser1 );
-
- // Copy the temp graphs to the real ones...
- inCrosser1 -> RuntimeCopy( mCrossTemp1, false );
- inCrosser2 -> RuntimeCopy( mCrossTemp2, false );
- }
-
- // ---------------------------------------------------------------------------
- // • TerminationCondition (PRIVATE)
- //
- // Called by: CPopulation::DoIterate
- // ---------------------------------------------------------------------------
-
- Boolean
- CPopulation::TerminationCondition( )
- {
- return (
-
- ( mTermination.stopMaxGenTotal &&
- mCurGeneration >= mTermination.maxGenTotal ) ||
-
- ( mTermination.stopMaxGenNoChange &&
- mCurGeneration - mBestGeneration >= mTermination.maxGenNoChange ) ||
-
- ( mTermination.stopMaxTimeTotal &&
- mRunningTime >= mTermination.maxTimeTotal ) ||
-
- ( mTermination.stopMaxTimeNoChange &&
- mRunningTime - mBestTime >= mTermination.maxTimeNoChange ) ||
-
- ( mTermination.stopNoCrossings &&
- mLeastCrossings == 0L )
-
- );
- }
-
- // ---------------------------------------------------------------------------
- // • SetTermination
- //
- // Called by: CGraphGADoc::CGraphGADoc
- // CGraphGADoc::SetPopTermination
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::SetTermination( STermination &inTermination )
- {
- mTermination = inTermination;
-
- mTermCondExists = ( mTermination.stopMaxGenTotal ||
- mTermination.stopMaxGenNoChange ||
- mTermination.stopMaxTimeTotal ||
- mTermination.stopMaxTimeNoChange ||
- mTermination.stopNoCrossings
- );
- }
-
- // ---------------------------------------------------------------------------
- // • SetProbabilities
- //
- // Called by: CGraphGADoc::CGraphGADoc
- // CGraphGADoc::SetPopProbabilities
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::SetProbabilities(
- Int16 inCrossoverProb,
- Int16 inMutationProb )
- {
- mCrossoverProb = inCrossoverProb;
- mMutationProb = inMutationProb;
- }
-
- // ---------------------------------------------------------------------------
- // • SetSelection
- //
- // Called by: CGraphGADoc::CGraphGADoc
- // CGraphGADoc::ObeyCommand
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::SetSelection( SSelection &inSelection )
- {
- mSelection = inSelection;
-
- InitSelectionArray();
- }
-
- // ---------------------------------------------------------------------------
- // • SetSizes
- //
- // Called by: CGraphGADoc::CGraphGADoc
- // ---------------------------------------------------------------------------
-
- void
- CPopulation::SetSizes(
- Int16 inGridSize,
- Int16 inPopSize )
- {
- mGridSize = inGridSize;
- mPopSize = inPopSize;
- }